package com.ylp.sys.aop;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ylp.common.response.Result;
import com.ylp.sys.annotation.SysLogAnnotation;
import com.ylp.sys.auth.entity.UserInfo;
import com.ylp.sys.common.SysLogConstant;
import com.ylp.sys.domain.entity.SysLog;
import com.ylp.sys.service.SysLogService;
import com.ylp.sys.utils.IPUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * 系统日志切面
 *
 * @author ylp
 *
 */
@Aspect
@Component
public class SysLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
    private ThreadLocal<SysLog> sysLogThreadLocal = new ThreadLocal<>();

    @Autowired
    private Executor customThreadPoolTaskExecutor;

    @Autowired
    private SysLogService sysLogService;

    /**
     * 日志切点
     */
    @Pointcut("execution(public * com.ylp..*controller.*.*(..))")
    public void sysLogOperAspect() {
    }

    @Pointcut("execution(public * com.ylp.sys.auth.controller.*.*(..))")
    public void sysLogAuthAspect() {
    }

    // 定义一个组合切点
    @Pointcut("sysLogOperAspect() || sysLogAuthAspect()")
    public void combinedExecution() {}

    /**
     * 前置通知
     *
     * @param joinPoint
     */
    @Before(value = "combinedExecution()")
    public void doBefore(JoinPoint joinPoint) {
        //System.out.println("doBefore aop===============joinPoint===="+ joinPoint);
      try {
          HttpServletRequest request = ((ServletRequestAttributes) Objects
                  .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
          SysLog sysLog = new SysLog();
          // 创建人信息请根据实际项目获取方式获取
          //登录后拿用户信息
          if(StpUtil.isLogin()) {
              UserInfo userLoginInfo = (UserInfo) StpUtil.getSession().get("userInfo");
              sysLog.setOperName(userLoginInfo.getUsername());
          }
          sysLog.setStartTime(LocalDateTime.now());
          sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
          sysLog.setRequestParams(formatParams(request.getParameterMap()));
          sysLog.setRequestType(request.getMethod());
          sysLog.setRequestIp(IPUtils.getIpAddress(request));
          String userAgentStr = request.getHeader("User-Agent");
          sysLog.setUserAgent(userAgentStr);
          UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
          sysLog.setDeviceName(userAgent.getOs().getName());
          sysLog.setBrowserName(userAgent.getBrowser().getName());

          // 获取请求体参数
          Object[] args = joinPoint.getArgs();
          for(Object arg : args){
              try {
                  String jsonString = JSON.toJSONString(arg);
                  JSONObject jsonObject = JSON.parseObject(jsonString);
                  if (jsonObject.containsKey("password")) {
                      jsonObject.put("password", "*****");
                  }
                  System.out.println("参数=" + JSON.toJSONString(jsonObject));
                  sysLog.setRequestParams(sysLog.getRequestParams() + JSON.toJSONString(jsonObject));
              } catch (Exception e) {
                  //e.printStackTrace();
              }
          }

          // 获取日志注解
          MethodSignature signature = (MethodSignature)joinPoint.getSignature();
          SysLogAnnotation annotation = signature.getMethod().getAnnotation(SysLogAnnotation.class);
          if (annotation != null) {
              sysLog.setModule(annotation.module());
              sysLog.setOperDesc(annotation.operDesc());
          }
          sysLogThreadLocal.set(sysLog);
         // System.out.println("doBefore aop111111===============");

//          logger.info("开始计时: {}  URI: {}  IP: {}", sysLog.getStartTime(), sysLog.getRequestUri(), sysLog.getRequestIp());
      } catch (Exception e) {
          logger.error(e.getMessage());
      }
    }

    /**
     * 返回通知
     *
     * @param ret
     */
    @AfterReturning(pointcut = "combinedExecution()", returning = "ret")
    public void doAfterReturning(Object ret) {
       // System.out.println("doAfterReturning aop===============");
        try {
            SysLog sysLog = sysLogThreadLocal.get();
            sysLog.setLogType(SysLogConstant.LOG_INGO);
            sysLog.setEndTime(LocalDateTime.now());
            sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MILLIS.between(sysLog.getStartTime(), sysLog.getEndTime())));
            Result<?> r = Convert.convert(Result.class, ret);
//            if (SysLogConstant.TRUE.equals(String.valueOf(r.getCode()))) {
            if (r.getCode() == 0) {
                sysLog.setOperStatus(SysLogConstant.OPER_SUCCESS);
            } else {
                sysLog.setOperStatus(SysLogConstant.OPER_EXECPTION);
                sysLog.setExceptionInfo(r.getMessage());
            }
            sysLog.setResponseResult(JSON.toJSONString(r));
            customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
            sysLogThreadLocal.remove();

           // Runtime runtime = Runtime.getRuntime();
//            logger.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
//                    sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
//                    ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * 异常通知
     *
     * @param e
     */
    @AfterThrowing(pointcut = "combinedExecution()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        try {
            SysLog sysLog = sysLogThreadLocal.get();
            sysLog.setLogType(SysLogConstant.LOG_ERROR);
            sysLog.setEndTime(LocalDateTime.now());
            sysLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(sysLog.getStartTime(), sysLog.getEndTime())));
            sysLog.setOperStatus(SysLogConstant.OPER_EXECPTION);
            sysLog.setExceptionInfo(e.getMessage());
            customThreadPoolTaskExecutor.execute(new SaveLogThread(sysLog, sysLogService));
            sysLogThreadLocal.remove();

//            Runtime runtime = Runtime.getRuntime();
//            logger.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", sysLog.getEndTime(), sysLog.getExecuteTime(),
//                    sysLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
//                    ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
        } catch (Exception e1) {
            logger.error(e1.getMessage());
        }
    }

    /**
     * 格式化参数
     *
     * @param parameterMap
     * @return
     */
    private String formatParams(Map<String, String[]> parameterMap) {
        if (parameterMap == null) {
            return null;
        }
        StringBuilder params = new StringBuilder();
        for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
            if (params.length() != 0) {
                params.append("&");
            }
            params.append(param.getKey() + "=");
            if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
                params.append("*");
            } else if (param.getValue() != null) {
                params.append(ArrayUtil.join(param.getValue(), ","));
            }
        }
        return params.toString();
    }

    /**
     * 保存日志线程
     *
     * @author ylp
     */
    private static class SaveLogThread extends Thread {
        private SysLog sysLog;
        private SysLogService sysLogService;

        public SaveLogThread(SysLog sysLog, SysLogService sysLogService) {
            this.sysLog = sysLog;
            this.sysLogService = sysLogService;
        }

        @Override
        public void run() {
            try {
                sysLog.setCreateDate(LocalDateTime.now());
                String ipLocation = IPUtils.getIpRegion(sysLog.getRequestIp());
                logger.info("ip地址{}", ipLocation);
                sysLog.setOperLocation(ipLocation);
                sysLogService.save(sysLog);
            } catch (Exception e) {
                logger.error(e.getMessage());
            }
        }
    }
}